/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.citrus.maven.eclipse.base.eclipse.writers;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;

import com.alibaba.citrus.maven.eclipse.base.eclipse.Messages;
import com.alibaba.citrus.maven.eclipse.base.ide.IdeDependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;

/**
 * The <code>EclipseOSGiManifestWriter</code> ensures that value of the "Bundle-Classpath" property in
 * META-INF/MANIFEST.MF is synchronized with the POM by adding all dependencies that don't have the scope provided.
 *
 * @deprecated use <a href="http://cwiki.apache.org/FELIX/bundle-plugin-for-maven-bnd.html/">Maven Bundle plugin</a>
 *             from Felix
 */
public class EclipseOSGiManifestWriter
        extends AbstractEclipseWriter {

    /**
     * Constant used for newline.
     *
     * @todo check if we should use system-dependent newlines or if eclipse prefers a common format
     */
    private static final String NEWLINE = "\n";

    /** Bundle classpath: updated with the list of dependencies. */
    public final static String ENTRY_BUNDLE_CLASSPATH = "Bundle-ClassPath:";

    /** Bundle name: updated with the project name. */
    public final static String ENTRY_BUNDLE_NAME = "Bundle-Name:";

    /** Bundle symbolic name: updated with the artifact id. */
    public final static String ENTRY_BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName:";

    /** Bundle version: updated with the project version. */
    public final static String ENTRY_BUNDLE_VERSION = "Bundle-Version:";

    /** Bundle vendor: updated with the organization name (if set in the POM). */
    public final static String ENTRY_BUNDLE_VENDOR = "Bundle-Vendor:";

    /** @see com.alibaba.citrus.maven.eclipse.base.eclipse.writers.EclipseWriter#write() */
    public void write()
            throws MojoExecutionException {
        // check for existence
        if (!config.getOSGIManifestFile().exists()) {
            log.warn(Messages.getString("EclipseOSGiManifestWriter.nomanifestfile",
                                        config.getOSGIManifestFile().getAbsolutePath()));
            return;
        }

        StringBuffer manifestSb = rewriteManifest(config.getOSGIManifestFile());
        Writer out = null;
        try {
            out = new OutputStreamWriter(new FileOutputStream(config.getOSGIManifestFile()), "UTF-8");
            out.write(manifestSb.toString());
        } catch (FileNotFoundException e) {
            throw new MojoExecutionException(Messages.getString("EclipsePlugin.cantwritetofile",
                                                                config.getOSGIManifestFile().getAbsolutePath()));
        } catch (IOException e) {
            throw new MojoExecutionException(Messages.getString("EclipsePlugin.cantwritetofile",
                                                                config.getOSGIManifestFile().getAbsolutePath()), e);
        } finally {
            IOUtil.close(out);
        }
    }

    protected StringBuffer rewriteManifest(File manifestFile)
            throws MojoExecutionException {

        // warning: we read and rewrite the file line by line in order to preserve formatting
        boolean inBundleClasspathEntry = false;
        StringBuffer manifestSb = new StringBuffer();
        try {
            BufferedReader in =
                    new BufferedReader(new InputStreamReader(new FileInputStream(manifestFile), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                if (inBundleClasspathEntry && line.indexOf(":") > -1) {
                    inBundleClasspathEntry = false;
                } else if (inBundleClasspathEntry) {
                    // skip it
                    continue;
                }

                // Note that this could be the empty string, if we encounter
                // a field that we weren't expecting to be multi-line.
                String name = line.substring(0, line.indexOf(":") + 1);

                if (name.equalsIgnoreCase(ENTRY_BUNDLE_CLASSPATH)) {
                    inBundleClasspathEntry = true;
                } else if (name.equalsIgnoreCase(ENTRY_BUNDLE_NAME)) {
                    manifestSb.append(ENTRY_BUNDLE_NAME);
                    manifestSb.append(" ");
                    manifestSb.append(config.getProject().getName());
                    manifestSb.append(NEWLINE);
                } else if (name.equalsIgnoreCase(ENTRY_BUNDLE_SYMBOLICNAME)) {
                    manifestSb.append(ENTRY_BUNDLE_SYMBOLICNAME);
                    manifestSb.append(" ");
                    manifestSb.append(config.getEclipseProjectName());
                    manifestSb.append(";singleton:=true");
                    manifestSb.append(NEWLINE);
                } else if (name.equalsIgnoreCase(ENTRY_BUNDLE_VERSION)) {
                    manifestSb.append(ENTRY_BUNDLE_VERSION);
                    manifestSb.append(" ");
                    manifestSb.append(getNormalizedVersion(config.getProject().getVersion()));
                    manifestSb.append(NEWLINE);
                } else if (name.equalsIgnoreCase(ENTRY_BUNDLE_VENDOR) && config.getProject().getOrganization() != null) {
                    manifestSb.append(ENTRY_BUNDLE_VENDOR);
                    manifestSb.append(" ");
                    manifestSb.append(config.getProject().getOrganization().getName());
                    manifestSb.append(NEWLINE);
                } else {
                    manifestSb.append(line + NEWLINE);
                }
            }

            IOUtil.close(in);
        } catch (IOException e) {
            throw new MojoExecutionException(Messages.getString("EclipsePlugin.cantreadfile",
                                                                manifestFile.getAbsolutePath()));
        }
        manifestSb.append(addBundleClasspathEntries());

        // OSGi manifest headers need to end with a line break
        manifestSb.append(NEWLINE);
        return manifestSb;
    }

    /**
     * Normalize a version number, by moving snapshot identifier to the 5th token (first 4 tokens must be numeric for
     * OSGI bundles)
     *
     * @param version original version
     * @return a normalized version number
     */
    protected static String getNormalizedVersion(String version) {

        if (version.endsWith("-SNAPSHOT")) {
            String[] versionTokens = StringUtils.split(StringUtils.stripEnd(version, "-SNAPSHOT"), ".");

            int j = 0;
            StringBuffer newVersion = new StringBuffer(20);
            for (; j < versionTokens.length; j++) {
                newVersion.append(versionTokens[j]);
                newVersion.append(".");
            }
            for (; j < 3; j++) {
                newVersion.append("0.");
            }

            newVersion.append("SNAPSHOT");
            version = newVersion.toString();
        }
        return version;
    }

    /**
     * Add all libraries that don't have the scope "provided" to the "Bundle-Classpath".
     *
     * @return complete "Bundle-ClassPath:" entry for manifest
     */
    protected String addBundleClasspathEntries() {
        StringBuffer bundleClasspathSb = new StringBuffer(ENTRY_BUNDLE_CLASSPATH);

        // local classes, if the plugin is jarred
        // @todo handle expanded plugins
        bundleClasspathSb.append(" .");

        IdeDependency[] deps = config.getDeps();

        for (int j = 0; j < deps.length; j++) {
            IdeDependency dep = deps[j];
            if (!dep.isProvided() && !dep.isReferencedProject() && !dep.isTestDependency() && !dep.isOsgiBundle()) {
                bundleClasspathSb.append("," + NEWLINE);

                log.debug("Adding artifact to manifest: " + dep.getArtifactId());

                bundleClasspathSb.append(" " + getDependencyPathForPde(dep.getFile().getName()));
            }
        }
        // only insert the name of the property if there are local libraries
        return bundleClasspathSb.toString();
    }

    protected String getDependencyPathForPde(String name) {
        return name;
    }
}